package apibasej.basic.misc.standalone;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Collection;

public class UtilMath {

	/*
	public static void main(String[] args) {
		byte b = 0;
		
		Number b2 = b;
		
		System.out.println(""+( b2 instanceof Byte) );
		
		int i = 8;
		
		Object o1 = i;
		
		System.out.println(""+( o1 instanceof Number) );
		
		System.out.println(""+new BigDecimal("91999999999999999999999999999999999999999999999919.88888888888888888888888888").toBigInteger());
		
		System.out.println(Long.MAX_VALUE);
		System.out.println( BigDecimal.valueOf(Double.MAX_VALUE).toBigInteger() );
		
		System.out.println( Long.MAX_VALUE==Double.MAX_VALUE );
		System.out.println( Long.MAX_VALUE<Double.MAX_VALUE );
		System.out.println( Long.MAX_VALUE>Double.MAX_VALUE );
		
		byte b3 = getValByNumberType(byte.class, 1);
		
		//Long l = b3;
		
		System.out.println(""+((Number)b3));
		
		System.out.println("--------------------------------------------------");
		System.out.println(roundFractionDigits(12.123456789012345, 0));
		System.out.println(roundFractionDigits(12.123456789012345, 1));
		System.out.println(roundFractionDigits(12.123456789012345, 2));
		System.out.println(roundFractionDigits(12.123456789012345, 3));
		System.out.println(roundFractionDigits(12.123456789012345, 6));
		System.out.println(roundFractionDigits(12.123456789012345, 12));
	}
	public static void main(String[] args) {
		double valUnit = 12.1200001;
		System.out.println(Math.ceil(valUnit*100)/100);
	}
	public static void main(String[] args) {
		System.out.println(Math.ceil(980));
		System.out.println(Math.ceil(980D));
		System.out.println(Math.ceil(9.8D*100));
		System.out.println(Math.ceil(9.8D*100)/100);
		
		System.out.println(Math.ceil(980));
		System.out.println(Math.ceil((int)(9.8*100)));
		System.out.println(Math.ceil((int)(9.8*100))/100);
		
		System.out.println(new BigDecimal(12.0000002).setScale(2, RoundingMode.UP).doubleValue());
		System.out.println(new BigDecimal(9.8D).setScale(2, RoundingMode.UP).doubleValue());
		System.out.println(new BigDecimal(9.8D).setScale(2, RoundingMode.CEILING).doubleValue());
		
		MathContext mc = new MathContext(8,RoundingMode.UP);
		System.out.println(new BigDecimal(9.8).round(mc));//9.8000001
		System.out.println(BigDecimal.valueOf(9.8).round(mc));//9.8
		System.out.println(new BigDecimal("9.80000000000").round(mc));//9.8000000
		System.out.println(BigDecimal.valueOf(9.80000000).round(mc));//9.8
		
	}
	*/
	
	public static void main(String[] args) {
		System.out.println(BigDecimal.valueOf(0.005).setScale(2, RoundingMode.HALF_DOWN).doubleValue());
		System.out.println(BigDecimal.valueOf(0.005).setScale(2, RoundingMode.HALF_EVEN).doubleValue());
		System.out.println(BigDecimal.valueOf(0.005).setScale(2, RoundingMode.HALF_UP).doubleValue());
		System.out.println(BigDecimal.valueOf(0.0049).setScale(2, RoundingMode.HALF_UP).doubleValue());
		System.out.println(BigDecimal.valueOf(0.0051).setScale(2, RoundingMode.HALF_UP).doubleValue());
		System.out.println("--------------");
		System.out.println(BigDecimal.valueOf(-0.005).setScale(2, RoundingMode.HALF_DOWN).doubleValue());
		System.out.println(BigDecimal.valueOf(-0.005).setScale(2, RoundingMode.HALF_EVEN).doubleValue());
		System.out.println(BigDecimal.valueOf(-0.005).setScale(2, RoundingMode.HALF_UP).doubleValue());
		System.out.println(BigDecimal.valueOf(-0.0049).setScale(2, RoundingMode.HALF_UP).doubleValue());
		System.out.println(BigDecimal.valueOf(-0.0051).setScale(2, RoundingMode.HALF_UP).doubleValue());
	}
	
	
	public static double roundFractionDigits(double val, int qtdFractionDigits){
		//usar sempre BigDecimal para operações! pois operações com double nunca grante a precisão exata (EX: algo que deveria retornar 3 exato pode retornar 2.999999999...)
		//usar HALF_UP para 0,005 mostrar 0,01 (HALF_EVEN mostra 0,00)
		return BigDecimal.valueOf(val).setScale(qtdFractionDigits, RoundingMode.HALF_UP).doubleValue();
		
		/* SEMPRE USAR BigDecimal.valueOf() INVÉS DE new BigDecimal() POR CAUSA DO PROBLEMA ABAIXO
		MathContext mc = new MathContext(8,RoundingMode.UP);
		System.out.println(new BigDecimal(9.8).round(mc));//     9.8000001
		System.out.println(BigDecimal.valueOf(9.8).round(mc));// 9.8
		System.out.println(new BigDecimal("9.80000000000").round(mc));// 9.8000000
		 */
		
		/* não funciona bem! coloca 1 sem precisar! EX: 9.8  fica 9.800001 (6 casas!)
		double fator = Math.pow(10, qtdFractionDigits);
		return Math.round(val*fator)/fator;
		*/
	}
	
	public static double sum(double... vals){
		return sum(double.class,vals);
	}
	
	public static <T extends Number> T sum(Class<T> typeResp, Collection<?> vals){
		return sum(typeResp, vals.toArray());
	}
	public static <T extends Number> T sum(Class<T> typeResp, Object... vals){
		if(vals==null){
			return getValByNumberType(typeResp,0);//null;//retornar 0 ????
		}
		if(vals.length==0){
			return getValByNumberType(typeResp,0);
		}
		
		//divide por causa do desempenho
		if(BigInteger.class.equals(typeResp) || BigDecimal.class.equals(typeResp)){
			
			BigDecimal s = new BigDecimal(0);
			for(Object v : vals){
				//validadeNumber(v);//não perder desempenho com isto ... se não for deixar o ClassCastException
				BigDecimal d = getValByNumberType(BigDecimal.class, (Number)v);
				if(d==null){
					d = new BigDecimal(0);
				}
				s = s.add(d);
			}
			return getValByNumberType(typeResp, s);
			
		}else{ // se não for BigDecimal nem BigInteger, deve caber em um double!   (Long.MAX_VALUE<Double.MAX_VALUE == true!) o double é maior que o long
			
			double s = 0;
			for(Object v : vals){
				//validadeNumber(v);//não perder desempenho com isto ... se não for deixar o ClassCastException
				Double d = getValByNumberType(double.class, (Number)v);
				if(d==null){
					d = 0D;
				}
				s += d;
			}
			return getValByNumberType(typeResp, s);
		}

	}
	
	@SuppressWarnings("unchecked")
	public static <T> T getValByNumberType(Class<T> typeResp, Number n){
		
		if(n==null){
			return null;
			
		}else if(byte.class.equals(typeResp)   || Byte.class.equals(typeResp)){
			return (T)Byte.valueOf(n.byteValue());
		}else if(double.class.equals(typeResp) || Double.class.equals(typeResp)){
			return (T)Double.valueOf(n.doubleValue());
		}else if(float.class.equals(typeResp)  || Float.class.equals(typeResp)){
			return (T)Float.valueOf(n.floatValue());
		}else if(int.class.equals(typeResp)    || Integer.class.equals(typeResp)){
			return (T)Integer.valueOf(n.intValue());
		}else if(long.class.equals(typeResp)   || Long.class.equals(typeResp)){
			return (T)Long.valueOf(n.longValue());
		}else if(short.class.equals(typeResp)  || Short.class.equals(typeResp)){
			return (T)Short.valueOf(n.shortValue());
			
		}else if(BigDecimal.class.equals(typeResp)){
			//TODO para melhor desempenho verificar os tipos ??? para não precisar transformar para string ???
			if(n instanceof BigDecimal){
				return (T)n;
			}else if(n instanceof BigInteger){
				return (T)new BigDecimal((BigInteger)n);
			}else{
				return (T)new BigDecimal(n.toString());
			}
		}else if(BigInteger.class.equals(typeResp)){
			if(n instanceof BigInteger){
				return (T)n;
			}else{
				return (T)new BigDecimal(n.toString()).toBigInteger();
			}
		}else{
			throw new IllegalArgumentException("Invalid number type for return: "+typeResp);
		}
	}
	
	
	
	/* 
	public static void validadeNumber(Object v){
		if(!(v instanceof Number)){
			throw new IllegalArgumentException("Value must be instance of Number. Value: "+v+", Value Class: "+v.getClass());
		}
	}*/
	/*
	public static boolean isDecimal(Class<?> typeNum){
		return double.class.equals(typeNum) 
				|| Double.class.equals(typeNum) 
				|| float.class.equals(typeNum)  
				|| Float.class.equals(typeNum)
				|| BigDecimal.class.equals(typeNum);
	}*/

}
